Skip to content

14. render props ​

本节目标:理解“14. render props”的核心思路,并能把它用于实际开发或面试表达。

  •   render-props
  • render prop 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术

  • 具有 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑

  • render prop 是一个用于告知组件需要渲染什么内容的函数 prop

  • 这也是逻辑复用的一种方式

14.1 原生实现


import React from 'react';import ReactDOM from 'react-dom';class MouseTracker extends React.Component {    constructor(props) {        super(props);        this.state = { x: 0, y: 0 };    }
handleMouseMove = (event) => {        this.setState({            x: event.clientX,            y: event.clientY        });    }
render() {        return (            <div onMouseMove={this.handleMouseMove}>                <h1>

移动鼠标

!</h1>                <p>

当前的鼠标位置是

 ({this.state.x}, {this.state.y})</p>            </div>        );    }}ReactDOM.render(<MouseTracker />, document.getElementById('root'));
14.2 children
  • children是一个渲染的方法
import React from './react';import ReactDOM from './react-dom';
class MouseTracker extends React.Component {    constructor(props) {        super(props);        this.state = { x: 0, y: 0 };    }
handleMouseMove = (event) => {        this.setState({            x: event.clientX,            y: event.clientY        });    }
render() {        return (            <div onMouseMove={this.handleMouseMove}>                {this.props.children(this.state)}            </div>        );    }}ReactDOM.render(<MouseTracker >    {        (props) => (            <div>                <h1>

移动鼠标

!</h1>                <p>

当前的鼠标位置是

 ({props.x}, {props.y})</p>            </div>        )    }</MouseTracker >, document.getElementById('root'));
14.3 render

属性


import React from 'react';import ReactDOM from 'react-dom';class MouseTracker extends React.Component {    constructor(props) {        super(props);        this.state = { x: 0, y: 0 };    }
handleMouseMove = (event) => {        this.setState({            x: event.clientX,            y: event.clientY        });    }
render() {        return (            <div onMouseMove={this.handleMouseMove}>                {this.props.render(this.state)}            </div>        );    }}
ReactDOM.render(< MouseTracker render={params => (    <>        <h1>

移动鼠标

!</h1>        <p>

当前的鼠标位置是

 ({params.x}, {params.y})</p>    </>)} />, document.getElementById('root'));
14.4 HOC
import React from 'react';import ReactDOM from 'react-dom';function withTracker(OldComponent){  return class MouseTracker extends React.Component{    constructor(props){        super(props);        this.state = {x:0,y:0};    }    handleMouseMove = (event)=>{        this.setState({            x:event.clientX,            y:event.clientY        });    }    render(){        return (            <div onMouseMove = {this.handleMouseMove}>               <OldComponent {...this.state}/>            </div>        )    } }}//renderfunction Show(props){    return (        <React.Fragment>          <h1>

请移动鼠标

</h1>          <p>

当前鼠标的位置是

: x:{props.x} y:{props.y}</p>        </React.Fragment>    )}let HighShow = withTracker(Show);ReactDOM.render(    <HighShow/>, document.getElementById('root'));
15.

性能优化


15.1 src\index.js
import React from './react';import ReactDOM from './react-dom';class ClassCounter extends React.PureComponent {    render() {        console.log('ClassCounter render');        return <div>ClassCounter:{this.props.count}</div>    }}function FunctionCounter(props) {    console.log('FunctionCounter render'); debugger    return <div>FunctionCounter:{props.count}</div>}const MemoFunctionCounter = React.memo(FunctionCounter);class App extends React.Component {    state = { number: 0 }    amountRef = React.createRef()    handleClick = () => {        let nextNumber = this.state.number + parseInt(this.amountRef.current.value);        this.setState({ number: nextNumber });    }    render() {        return (            <div>                <ClassCounter count={this.state.number} />                <MemoFunctionCounter count={this.state.number} />                <input ref={this.amountRef} />                <button onClick={this.handleClick}>+</button>            </div>        )    }}ReactDOM.render(    <App />, document.getElementById('root'));
15.2 src\constants.js
src\constants.js
export const REACT_TEXT = Symbol('REACT_TEXT');export const REACT_FORWARD_REF_TYPE = Symbol('react.forward_ref');
export const PLACEMENT = 'PLACEMENT';export const MOVE = 'MOVE';
export const REACT_CONTEXT = Symbol('react.context');export const REACT_PROVIDER = Symbol('react.provider');+export const REACT_MEMO = Symbol('react.memo')
15.3 src\utils.js
src\utils.js
import { REACT_TEXT } from "./constants";export function wrapToVdom(element) {  return typeof element === "string" || typeof element === "number"    ? { type: REACT_TEXT, props: { content: element } }    : element;}
export function isFunction(obj) {  return typeof obj === 'function';}
+export function shallowEqual(obj1, obj2) {+  if (obj1 === obj2) {+    return true;+  }+  if (typeof obj1 != "object" || obj1 === null || typeof obj2 != "object" || obj2 === null) {+    return false;+  }+  let keys1 = Object.keys(obj1);+  let keys2 = Object.keys(obj2);+  if (keys1.length !== keys2.length) {+    return false;+  }+  for (let key of keys1) {+    if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {+      return false;+    }+  }+  return true;+}
15.4 src\react.js
src\react.js
+import { wrapToVdom, shallowEqual } from "./utils";import { Component } from './Component';+import { REACT_FORWARD_REF_TYPE, REACT_CONTEXT, REACT_PROVIDER, REACT_MEMO } from './constants';function createElement(type, config, children) {  let ref;  let key;  if (config) {    delete config.__source;    delete config.__self;    ref = config.ref;    delete config.ref;    key = config.key;    delete config.key;  }  let props = { ...config };  if (arguments.length > 3) {    props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);  } else {    props.children = wrapToVdom(children);  }  return {    type,    ref,    key,    props,  };}function createRef() {  return { current: null };}function forwardRef(render) {  var elementType = {    $$typeof: REACT_FORWARD_REF_TYPE,    render: render  };  return elementType;}function Fragment(props) {  return props.children;}function createContext() {  let context = { $$typeof: REACT_CONTEXT };  context.Provider = {    $$typeof: REACT_PROVIDER,    _context: context  }  context.Consumer = {    $$typeof: REACT_CONTEXT,    _context: context  }  return context;}function cloneElement(element, newProps, ...newChildren) {  let oldChildren = element.props && element.props.children;  let children = [...(Array.isArray(oldChildren) ? oldChildren : [oldChildren]), ...newChildren]    .filter(item => item !== undefined)    .map(wrapToVdom);  if (children.length === 1) children = children[0];  let props = { ...element.props, ...newProps, children };  return { ...element, props };}+class PureComponent extends Component {+  shouldComponentUpdate(newProps, nextState) {+    return !shallowEqual(this.props, newProps) || !shallowEqual(this.state, nextState);+  }+}+function memo(type, compare = shallowEqual) {+  return {+    $$typeof: REACT_MEMO,+    type,+    compare+  }+}const React = {  createElement,  Component,  createRef,  forwardRef,  Fragment,  createContext,  cloneElement,  PureComponent,+ memo};export default React;
15.5 src\react-dom.js
src\react-dom.js
+import { REACT_TEXT, REACT_FORWARD_REF_TYPE, PLACEMENT, MOVE, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "./constants";import { addEvent } from "./event";import React from './react';function render(vdom, container) {  mount(vdom, container);}export function mount(vdom, container) {  let newDOM = createDOM(vdom);  container.appendChild(newDOM);  if (newDOM.componentDidMount) newDOM.componentDidMount();}export function createDOM(vdom) {  let { type, props, ref } = vdom;  let dom;+  if (type && type.$$typeof === REACT_MEMO) {+    return mountMemoComponent(vdom);+  } else if (type && type.$$typeof === REACT_PROVIDER) {    return mountProviderComponent(vdom)  } else if (type && type.$$typeof === REACT_CONTEXT) {    return mountContextComponent(vdom)  } else if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) {    return mountForwardComponent(vdom);  } else if (type === REACT_TEXT) {    dom = document.createTextNode(props.content);  } else if (typeof type === "function") {    if (type.isReactComponent) {      return mountClassComponent(vdom);    } else {      if (type === React.Fragment) {        dom = document.createDocumentFragment();      } else {        return mountFunctionComponent(vdom);      }    }  } else {    dom = document.createElement(type);  }  if (props) {    updateProps(dom, {}, props);    if (typeof props.children == "object" && props.children.type) {      props.children.mountIndex = 0;      mount(props.children, dom);    } else if (Array.isArray(props.children)) {      reconcileChildren(props.children, dom);    }  }  vdom.dom = dom;  if (ref) ref.current = dom;  return dom;}
+function mountMemoComponent(vdom) {+  let { type, props } = vdom;+  let renderVdom = type.type(props);+  vdom.prevProps = props;+  vdom.oldRenderVdom = renderVdom;+  return createDOM(renderVdom);+}function mountProviderComponent(vdom) {  let { type, props } = vdom;  let context = type._context;  context._currentValue = props.value;  let renderVdom = props.children;  vdom.oldRenderVdom = renderVdom;  return createDOM(renderVdom);}function mountContextComponent(vdom) {  let { type, props } = vdom;  let context = type._context;  let renderVdom = props.children(context._currentValue);  vdom.oldRenderVdom = renderVdom;  return createDOM(renderVdom);}function mountForwardComponent(vdom) {  let { type, props, ref } = vdom;  let renderVdom = type.render(props, ref);  vdom.oldRenderVdom = renderVdom;  return createDOM(renderVdom);}function mountClassComponent(vdom) {  let { type, props, ref } = vdom;  let classInstance = new type(props);  if (type.contextType) {    classInstance.context = type.contextType._currentValue;  }  vdom.classInstance = classInstance;  if (ref) ref.current = classInstance;  if (classInstance.componentWillMount) classInstance.componentWillMount();  let renderVdom = classInstance.render();  classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;  let dom = createDOM(renderVdom);  if (classInstance.componentDidMount)    dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);  return dom;}function mountFunctionComponent(vdom) {  let { type, props } = vdom;  let renderVdom = type(props);  vdom.oldRenderVdom = renderVdom;  return createDOM(renderVdom);}function updateProps(dom, oldProps, newProps) {  for (let key in newProps) {    if (key === "children") {      continue;    }    if (key === "style") {      let style = newProps[key];      for (let attr in style) {        dom.style[attr] = style[attr];      }    } else if (key.startsWith("on")) {      addEvent(dom, key.toLocaleLowerCase(), newProps[key]);    } else {      dom[key] = newProps[key];    }  }}export function findDOM(vdom) {  let { type } = vdom;  let dom;+ if (type && type.$$typeof === REACT_MEMO) {+   dom = findDOM(vdom.oldRenderVdom);  } else if (type && type.$$typeof === REACT_PROVIDER) {    dom = findDOM(vdom.oldRenderVdom);  } else if (type && type.$$typeof === REACT_CONTEXT) {    dom = findDOM(vdom.oldRenderVdom);  } else if (typeof type === "function") {    if (type === React.Fragment) {      return vdom.dom;    } else {      dom = findDOM(vdom.oldRenderVdom);    }  } else {    dom = vdom.dom;  }  return dom;}function unMount(vdom) {  let currentDOM = findDOM(vdom);  if (vdom.classInstance && vdom.classInstance.componentWillUnmount) {    vdom.classInstance.componentWillUnmount();  }  if (vdom.props && vdom.props.ref) {    vdom.props.ref.current = null;  }  Object.keys(vdom.props).forEach((propName) => {    if (propName.slice(0, 2) === "on") {      const eventName = propName.toLowerCase().slice(0, 2);      currentDOM.removeEventListener(eventName, vdom.props[propName]);    }  });  if (vdom.props.children) {    let children = Array.isArray(vdom.props.children) ? vdom.props.children : [vdom.props.children];    children.forEach(unMount);  }  currentDOM.parentNode.removeChild(currentDOM);}export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) {  if (!oldVdom && !newVdom) {    //

老和新都是没有

    return;  } else if (!!oldVdom && !newVdom) {    //

老有新没有

    unMount(oldVdom);  } else if (!oldVdom && !!newVdom) {    //

老没有新的有

    let newDOM = createDOM(newVdom);    if (nextDOM) parentDOM.insertBefore(newDOM, nextDOM);    else parentDOM.appendChild(newDOM);    if (newDOM.componentDidMount) newDOM.componentDidMount();    return;  } else if (!!oldVdom && !!newVdom && oldVdom.type !== newVdom.type) {    //

新老都有,但类型不同

    let newDOM = createDOM(newVdom);    unMount(oldVdom);    if (newDOM.componentDidMount) newDOM.componentDidMount();  } else {    updateElement(oldVdom, newVdom);  }}function updateElement(oldVdom, newVdom) {+  if (oldVdom.type && oldVdom.type.$$typeof === REACT_MEMO) {+    updateMemoComponent(oldVdom, newVdom);  } else if (oldVdom.type.$$typeof === REACT_CONTEXT) {    updateContextComponent(oldVdom, newVdom);  } else if (oldVdom.type.$$typeof === REACT_PROVIDER) {    updateProviderComponent(oldVdom, newVdom);  } else if (oldVdom.type === REACT_TEXT && newVdom.type === REACT_TEXT) {    let currentDOM = newVdom.dom = findDOM(oldVdom);    if (oldVdom.props.content !== newVdom.props.content) {      currentDOM.textContent = newVdom.props.content;    }    return;  } else if (typeof oldVdom.type === 'string') {    let currentDOM = newVdom.dom = findDOM(oldVdom);    updateProps(currentDOM, oldVdom.props, newVdom.props);    updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);  } else if (typeof oldVdom.type === 'function') {    if (oldVdom.type.isReactComponent) {      newVdom.classInstance = oldVdom.classInstance;      updateClassInstance(oldVdom, newVdom);    } else {      if (oldVdom.type === React.Fragment) {        let currentDOM = newVdom.dom = findDOM(oldVdom);        updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);      } else {        updateFunctionComponent(oldVdom, newVdom);      }    }  }}+function updateMemoComponent(oldVdom, newVdom) {+  let { type, prevProps } = oldVdom;+  if (!type.compare(prevProps, newVdom.props)) {+    let oldDOM = findDOM(oldVdom);+    let parentDOM = oldDOM.parentNode;+    let { type, props } = newVdom;+    let renderVdom = type.type(props);+    compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);+    newVdom.prevProps = props;+    newVdom.oldRenderVdom = renderVdom;+  } else {+    newVdom.prevProps = prevProps;+    newVdom.oldRenderVdom = oldVdom.oldRenderVdom;+  }+}function updateProviderComponent(oldVdom, newVdom) {  let parentDOM = findDOM(oldVdom).parentNode;  let { type, props } = newVdom;  let context = type._context;  context._currentValue = props.value;  let renderVdom = props.children;  compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);  newVdom.oldRenderVdom = renderVdom;}function updateContextComponent(oldVdom, newVdom) {  let parentDOM = findDOM(oldVdom).parentNode;  let { type, props } = newVdom;  let context = type._context;  let renderVdom = props.children(context._currentValue);  compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);  newVdom.oldRenderVdom = renderVdom;}function updateFunctionComponent(oldVdom, newVdom) {  let parentDOM = findDOM(oldVdom).parentNode;  let { type, props } = newVdom;  let newRenderVdom = type(props);  compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);  newVdom.oldRenderVdom = newRenderVdom;}function updateClassInstance(oldVdom, newVdom) {  let classInstance = newVdom.classInstance = oldVdom.classInstance;  newVdom.oldRenderVdom = oldVdom.oldRenderVdom;  if (classInstance.componentWillReceiveProps) {    classInstance.componentWillReceiveProps();  }  classInstance.updater.emitUpdate(newVdom.props);}function updateChildren(parentDOM, oldVChildren, newVChildren) {  oldVChildren = Array.isArray(oldVChildren) ? oldVChildren : oldVChildren ? [oldVChildren] : [];  newVChildren = Array.isArray(newVChildren) ? newVChildren : newVChildren ? [newVChildren] : [];  let keyedOldMap = {};  let lastPlacedIndex = 0;  oldVChildren.forEach((oldVChild, index) => {    let oldKey = oldVChild.key ? oldVChild.key : index;    keyedOldMap[oldKey] = oldVChild;  });  let patch = [];  newVChildren.forEach((newVChild, index) => {    newVChild.mountIndex = index;    let newKey = newVChild.key ? newVChild.key : index;    let oldVChild = keyedOldMap[newKey];    if (oldVChild) {      updateElement(oldVChild, newVChild);      if (oldVChild.mountIndex < lastPlacedIndex) {        patch.push({          type: MOVE,          oldVChild,          newVChild,          mountIndex: index        });      }      delete keyedOldMap[newKey];      lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex);    } else {      patch.push({        type: PLACEMENT,        newVChild,        mountIndex: index      });    }  });  let moveVChild = patch.filter(action => action.type === MOVE).map(action => action.oldVChild);  Object.values(keyedOldMap).concat(moveVChild).forEach((oldVChild) => {    let currentDOM = findDOM(oldVChild);    parentDOM.removeChild(currentDOM);  });  patch.forEach(action => {    let { type, oldVChild, newVChild, mountIndex } = action;    let childNodes = parentDOM.childNodes;    if (type === PLACEMENT) {      let newDOM = createDOM(newVChild);      let childNode = childNodes[mountIndex];      if (childNode) {        parentDOM.insertBefore(newDOM, childNode);      } else {        parentDOM.appendChild(newDOM);      }    } else if (type === MOVE) {      let oldDOM = findDOM(oldVChild);      let childNode = childNodes[mountIndex];      if (childNode) {        parentDOM.insertBefore(oldDOM, childNode);      } else {        parentDOM.appendChild(oldDOM);      }    }  });}function reconcileChildren(childrenVdom, parentDOM) {  for (let i = 0; i < childrenVdom.length; i++) {    let childVdom = childrenVdom[i];    childVdom.mountIndex = i;    mount(childVdom, parentDOM);  }}const ReactDOM = {  render,};export default ReactDOM;
16.Portal
  • React v16增加了对Portal的直接支持
  • 它可以把JSX渲染到一个单独的DOM节点中
16.1 src\index.js
src\index.js
import React from './react';import ReactDOM from './react-dom';class Dialog extends React.Component {    constructor(props) {        super(props);        this.node = document.createElement('div');        document.body.appendChild(this.node);    }    render() {        ReactDOM.createPortal(            <div className="dialog">                {this.props.children}            </div>,            this.node        );        return null;    }    componentWillUnmount() {        window.document.body.removeChild(this.node);    }}class App extends React.Component {    render() {        return (            <div>                <Dialog>

模态窗

</Dialog>            </div>        )    }}ReactDOM.render(    <App />, document.getElementById('root'));
16.2 src\react-dom.js
src\react-dom.js
import { REACT_TEXT, REACT_FORWARD_REF_TYPE, PLACEMENT, MOVE, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "./constants";import { addEvent } from "./event";import React from './react';function render(vdom, container) {  mount(vdom, container);}+function createPortal(vdom, container) {+  mount(vdom, container);+}export function mount(vdom, container) {  let newDOM = createDOM(vdom);+  if (newDOM) {    container.appendChild(newDOM);    if (newDOM.componentDidMount) newDOM.componentDidMount();+  }}export function createDOM(vdom) {  let { type, props, ref } = vdom;  let dom;  if (type && type.$$typeof === REACT_MEMO) {    return mountMemoComponent(vdom);  } else if (type && type.$$typeof === REACT_PROVIDER) {    return mountProviderComponent(vdom)  } else if (type && type.$$typeof === REACT_CONTEXT) {    return mountContextComponent(vdom)  } else if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) {    return mountForwardComponent(vdom);  } else if (type === REACT_TEXT) {    dom = document.createTextNode(props.content);  } else if (typeof type === "function") {    if (type.isReactComponent) {      return mountClassComponent(vdom);    } else {      if (type === React.Fragment) {        dom = document.createDocumentFragment();      } else {        return mountFunctionComponent(vdom);      }    }  } else {    dom = document.createElement(type);  }  if (props) {    updateProps(dom, {}, props);    if (typeof props.children == "object" && props.children.type) {      props.children.mountIndex = 0;      mount(props.children, dom);    } else if (Array.isArray(props.children)) {      reconcileChildren(props.children, dom);    }  }  vdom.dom = dom;  if (ref) ref.current = dom;  return dom;}
function mountMemoComponent(vdom) {  let { type, props } = vdom;  let renderVdom = type.type(props);  vdom.prevProps = props;  vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null;    return createDOM(renderVdom);}function mountProviderComponent(vdom) {  let { type, props } = vdom;  let context = type._context;  context._currentValue = props.value;  let renderVdom = props.children;  vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null;    return createDOM(renderVdom);}function mountContextComponent(vdom) {  let { type, props } = vdom;  let context = type._context;  let renderVdom = props.children(context._currentValue);  vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null;    return createDOM(renderVdom);}function mountForwardComponent(vdom) {  let { type, props, ref } = vdom;  let renderVdom = type.render(props, ref);  vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null;    return createDOM(renderVdom);}function mountClassComponent(vdom) {  let { type, props, ref } = vdom;  let classInstance = new type(props);  if (type.contextType) {    classInstance.context = type.contextType._currentValue;  }  vdom.classInstance = classInstance;  if (ref) ref.current = classInstance;  if (classInstance.componentWillMount) classInstance.componentWillMount();  let renderVdom = classInstance.render();  classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null;  let dom = createDOM(renderVdom);  if (classInstance.componentDidMount)    dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);  return dom;}function mountFunctionComponent(vdom) {  let { type, props } = vdom;  let renderVdom = type(props);  vdom.oldRenderVdom = renderVdom;+ if (!renderVdom) return null;  return createDOM(renderVdom);}function updateProps(dom, oldProps, newProps) {  for (let key in newProps) {    if (key === "children") {      continue;    }    if (key === "style") {      let style = newProps[key];      for (let attr in style) {        dom.style[attr] = style[attr];      }    } else if (key.startsWith("on")) {      addEvent(dom, key.toLocaleLowerCase(), newProps[key]);    } else {      dom[key] = newProps[key];    }  }}export function findDOM(vdom) {  let { type } = vdom;  let dom;  if (type && type.$$typeof === REACT_MEMO) {    dom = findDOM(vdom.oldRenderVdom);  } else if (type && type.$$typeof === REACT_PROVIDER) {    dom = findDOM(vdom.oldRenderVdom);  } else if (type && type.$$typeof === REACT_CONTEXT) {    dom = findDOM(vdom.oldRenderVdom);  } else if (typeof type === "function") {    if (type === React.Fragment) {      return vdom.dom;    } else {      dom = findDOM(vdom.oldRenderVdom);    }  } else {    dom = vdom.dom;  }  return dom;}function unMount(vdom) {  let currentDOM = findDOM(vdom);  if (vdom.classInstance && vdom.classInstance.componentWillUnmount) {    vdom.classInstance.componentWillUnmount();  }  if (vdom.props && vdom.props.ref) {    vdom.props.ref.current = null;  }  Object.keys(vdom.props).forEach((propName) => {    if (propName.slice(0, 2) === "on") {      const eventName = propName.toLowerCase().slice(0, 2);      currentDOM.removeEventListener(eventName, vdom.props[propName]);    }  });  if (vdom.props.children) {    let children = Array.isArray(vdom.props.children) ? vdom.props.children : [vdom.props.children];    children.forEach(unMount);  }  currentDOM.parentNode.removeChild(currentDOM);}export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) {  if (!oldVdom && !newVdom) {    //

老和新都是没有

    return;  } else if (!!oldVdom && !newVdom) {    //

老有新没有

    unMount(oldVdom);  } else if (!oldVdom && !!newVdom) {    //

老没有新的有

    let newDOM = createDOM(newVdom);    if (nextDOM) parentDOM.insertBefore(newDOM, nextDOM);    else parentDOM.appendChild(newDOM);    if (newDOM.componentDidMount) newDOM.componentDidMount();    return;  } else if (!!oldVdom && !!newVdom && oldVdom.type !== newVdom.type) {    //

新老都有,但类型不同

    let newDOM = createDOM(newVdom);    unMount(oldVdom);    if (newDOM.componentDidMount) newDOM.componentDidMount();  } else {    updateElement(oldVdom, newVdom);  }}function updateElement(oldVdom, newVdom) {  if (oldVdom.type && oldVdom.type.$$typeof === REACT_MEMO) {    updateMemoComponent(oldVdom, newVdom);  } else if (oldVdom.type.$$typeof === REACT_CONTEXT) {    updateContextComponent(oldVdom, newVdom);  } else if (oldVdom.type.$$typeof === REACT_PROVIDER) {    updateProviderComponent(oldVdom, newVdom);  } else if (oldVdom.type === REACT_TEXT && newVdom.type === REACT_TEXT) {    let currentDOM = newVdom.dom = findDOM(oldVdom);    if (oldVdom.props.content !== newVdom.props.content) {      currentDOM.textContent = newVdom.props.content;    }    return;  } else if (typeof oldVdom.type === 'string') {    let currentDOM = newVdom.dom = findDOM(oldVdom);    updateProps(currentDOM, oldVdom.props, newVdom.props);    updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);  } else if (typeof oldVdom.type === 'function') {    if (oldVdom.type.isReactComponent) {      newVdom.classInstance = oldVdom.classInstance;      updateClassInstance(oldVdom, newVdom);    } else {      if (oldVdom.type === React.Fragment) {        let currentDOM = newVdom.dom = findDOM(oldVdom);        updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);      } else {        updateFunctionComponent(oldVdom, newVdom);      }    }  }}function updateMemoComponent(oldVdom, newVdom) {  let { type, prevProps } = oldVdom;  if (!type.compare(prevProps, newVdom.props)) {    let oldDOM = findDOM(oldVdom);    let parentDOM = oldDOM.parentNode;    let { type, props } = newVdom;    let renderVdom = type.type(props);    compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);    newVdom.prevProps = props;    newVdom.oldRenderVdom = renderVdom;  } else {    newVdom.prevProps = prevProps;    newVdom.oldRenderVdom = oldVdom.oldRenderVdom;  }}function updateProviderComponent(oldVdom, newVdom) {  let parentDOM = findDOM(oldVdom).parentNode;  let { type, props } = newVdom;  let context = type._context;  context._currentValue = props.value;  let renderVdom = props.children;  compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);  newVdom.oldRenderVdom = renderVdom;}function updateContextComponent(oldVdom, newVdom) {  let parentDOM = findDOM(oldVdom).parentNode;  let { type, props } = newVdom;  let context = type._context;  let renderVdom = props.children(context._currentValue);  compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);  newVdom.oldRenderVdom = renderVdom;}function updateFunctionComponent(oldVdom, newVdom) {  let parentDOM = findDOM(oldVdom).parentNode;  let { type, props } = newVdom;  let newRenderVdom = type(props);  compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);  newVdom.oldRenderVdom = newRenderVdom;}function updateClassInstance(oldVdom, newVdom) {  let classInstance = newVdom.classInstance = oldVdom.classInstance;  newVdom.oldRenderVdom = oldVdom.oldRenderVdom;  if (classInstance.componentWillReceiveProps) {    classInstance.componentWillReceiveProps();  }  classInstance.updater.emitUpdate(newVdom.props);}function updateChildren(parentDOM, oldVChildren, newVChildren) {  oldVChildren = Array.isArray(oldVChildren) ? oldVChildren : oldVChildren ? [oldVChildren] : [];  newVChildren = Array.isArray(newVChildren) ? newVChildren : newVChildren ? [newVChildren] : [];  let keyedOldMap = {};  let lastPlacedIndex = 0;  oldVChildren.forEach((oldVChild, index) => {    let oldKey = oldVChild.key ? oldVChild.key : index;    keyedOldMap[oldKey] = oldVChild;  });  let patch = [];  newVChildren.forEach((newVChild, index) => {    newVChild.mountIndex = index;    let newKey = newVChild.key ? newVChild.key : index;    let oldVChild = keyedOldMap[newKey];    if (oldVChild) {      updateElement(oldVChild, newVChild);      if (oldVChild.mountIndex < lastPlacedIndex) {        patch.push({          type: MOVE,          oldVChild,          newVChild,          mountIndex: index        });      }      delete keyedOldMap[newKey];      lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex);    } else {      patch.push({        type: PLACEMENT,        newVChild,        mountIndex: index      });    }  });  let moveVChild = patch.filter(action => action.type === MOVE).map(action => action.oldVChild);  Object.values(keyedOldMap).concat(moveVChild).forEach((oldVChild) => {    let currentDOM = findDOM(oldVChild);    parentDOM.removeChild(currentDOM);  });  patch.forEach(action => {    let { type, oldVChild, newVChild, mountIndex } = action;    let childNodes = parentDOM.childNodes;    if (type === PLACEMENT) {      let newDOM = createDOM(newVChild);      let childNode = childNodes[mountIndex];      if (childNode) {        parentDOM.insertBefore(newDOM, childNode);      } else {        parentDOM.appendChild(newDOM);      }    } else if (type === MOVE) {      let oldDOM = findDOM(oldVChild);      let childNode = childNodes[mountIndex];      if (childNode) {        parentDOM.insertBefore(oldDOM, childNode);      } else {        parentDOM.appendChild(oldDOM);      }    }  });}function reconcileChildren(childrenVdom, parentDOM) {  for (let i = 0; i < childrenVdom.length; i++) {    let childVdom = childrenVdom[i];    childVdom.mountIndex = i;    mount(childVdom, parentDOM);  }}const ReactDOM = {  render,  createPortal};export default ReactDOM;

参考


React

课程 > 来自

 <http://www.zhufengpeixun.com/strong/html/106.1.react.html>

本站总访问 — 次